/*
 * Copyright 2009 Armando Blancas
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package tests.taskgraph.pipes;

import java.io.EOFException;
import java.io.IOException;
import java.util.Arrays;

import junit.framework.Assert;
import junit.framework.JUnit4TestAdapter;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import taskgraph.pipes.FloatInputPipe;
import taskgraph.pipes.FloatOutputPipe;

public class FloatPipesTest {

	/**
	 * Runs the test suite in this class from the command line.
	 * 
	 * @param args	Arguments are ignored.
	 */
	public static void main(String[] args) {
		org.junit.runner.JUnitCore.runClasses(FloatPipesTest.class);
	}
	
	/**
	 * Provides compatibility with 3.x versions of JUnit.
	 * 
	 * @return A 3.x-compatible test suite.
	 */
	public static junit.framework.Test suite() {
		return new JUnit4TestAdapter(FloatPipesTest.class);
	}
	
	private static int DEFAULT_BUFFER_SIZE = 1024 * 32;

	private FloatInputPipe input;
	private FloatOutputPipe output;

    /*+----------------------------------------------------------------------+
	  |                                                                      | 
	  | Test the pipes constructors.                                         | 
	  |                                                                      | 
	  +----------------------------------------------------------------------+*/
	
	@Test
	public void defaultConstructor() {
		FloatInputPipe chp = new FloatInputPipe();
		Assert.assertNotNull(chp);
	}
	
	@Test
	public void defaultInstanceClass() {
		FloatInputPipe chp = new FloatInputPipe();
		Assert.assertEquals(FloatInputPipe.class, chp.getClass());
	}

	@Test
	public void constructorWithPipe() throws IOException {
		FloatInputPipe chp = new FloatInputPipe(new FloatOutputPipe());
		Assert.assertNotNull(chp);
	}
	
	@Test
	public void instanceClassWithPipe() throws IOException {
		FloatInputPipe chp = new FloatInputPipe(new FloatOutputPipe());
		Assert.assertEquals(FloatInputPipe.class, chp.getClass());
	}

	@Test
	public void constructorWithPipeAndSize() throws IOException {
		FloatInputPipe chp = new FloatInputPipe(
				                 new FloatOutputPipe(), DEFAULT_BUFFER_SIZE);
		Assert.assertNotNull(chp);
	}
	
	@Test
	public void instanceClassWithPipeAndSize() throws IOException {
		FloatInputPipe chp = new FloatInputPipe(
				                 new FloatOutputPipe(), DEFAULT_BUFFER_SIZE);
		Assert.assertEquals(FloatInputPipe.class, chp.getClass());
	}

	@Test
	public void constructorWithSize() {
		FloatInputPipe chp = new FloatInputPipe(DEFAULT_BUFFER_SIZE);
		Assert.assertNotNull(chp);
	}
	
	@Test
	public void instanceClassWithSize() {
		FloatInputPipe chp = new FloatInputPipe(DEFAULT_BUFFER_SIZE);
		Assert.assertEquals(FloatInputPipe.class, chp.getClass());
	}
	
	@Test
	public void defaultConstructors() throws IOException {
		FloatOutputPipe out = new FloatOutputPipe();
		FloatInputPipe in = new FloatInputPipe();
		Assert.assertNotNull(out);
		Assert.assertNotNull(in);
	}
	
	@Test
	public void outputConstructorWithPipe() throws IOException {
		FloatOutputPipe chp = new FloatOutputPipe(new FloatInputPipe());
		Assert.assertNotNull(chp);
	}
	
	@Test
	public void outputConstructorWithPipeAndSize() throws IOException {
		FloatOutputPipe chp = new FloatOutputPipe(
				                  new FloatInputPipe(DEFAULT_BUFFER_SIZE));
		Assert.assertNotNull(chp);
	}
	
	@Test
	public void connect() throws IOException {
		FloatOutputPipe out = new FloatOutputPipe();
		FloatInputPipe in = new FloatInputPipe();
		out.connect(in);
		// will call receive() on 'in' which checks for connection.
		out.write(3.1416f);
		// no IOExecption == test is OK -- just return.
	}
	
    /*+----------------------------------------------------------------------+
	  |                                                                      | 
	  | Test the write and read methods.                                     | 
	  |                                                                      | 
	  +----------------------------------------------------------------------+*/
	
	@Before
	public void setup() throws IOException {
		output = new FloatOutputPipe();
		input = new FloatInputPipe(output, DEFAULT_BUFFER_SIZE);
	}
	
	@After
	public void cleanup() throws IOException {
		input.close();
		output.close();
	}
	
	@Test
	public void receive() throws IOException {
		output.write(1.0f);
		output.write(2.0f);
		output.write(3.0f);
		output.write(4.0f);
		output.write(5.0f);
		output.write(6.0f);
		Assert.assertEquals(6, input.available());
	}
	
	@Test
	public void receiveHighLoad() throws IOException {
		final int size = DEFAULT_BUFFER_SIZE - 1024;
		for (int i = 0; i < size; ++i) {
			output.write(99.99f);
		}
		Assert.assertEquals(size, input.available());
	}
	
	@Test
	public void read() throws IOException {
		float[] data = { 
			1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f
		};
		output.write(data);
		for (float d : data)
			Assert.assertEquals(d, input.read());
	}
	
	@Test
	public void readArray() throws IOException {
		float[] data = { 
			1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f
		};
		output.write(data);
		float[] buffer = new float[data.length];
		input.read(buffer);
		for (int i = 0; i < data.length; ++i)
			Assert.assertEquals(data[i], buffer[i]);
	}
	
	@Test
	public void readSubArray() throws IOException {
		float[] data = { 
			1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f
		};
		float fill = 3.1415927f;
		output.write(data);

		float[] buffer = new float[data.length*2];
		Arrays.fill(buffer, fill);
		input.read(buffer, 10, 5);
		
		Assert.assertEquals(fill, buffer[9]);
		for (int i = 10; i < 15; ++i)
			Assert.assertEquals(data[i-10], buffer[i]);
		Assert.assertEquals(fill, buffer[15]);
	}
	
	@Test
	public void writeSubArray() throws IOException {
		float[] data = { 
			1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f
		};
		float fill = 3.1415927f;
		output.write(data, 3, 5);

		float[] buffer = new float[data.length*2];
		Arrays.fill(buffer, fill);
		input.read(buffer, 10, 5);
		
		Assert.assertEquals(fill, buffer[9]);
		for (int i = 10; i < 15; ++i)
			Assert.assertEquals(data[i-7], buffer[i]);
		Assert.assertEquals(fill, buffer[15]);
	}

	@Test
	public void skip() throws IOException {
		int size = 1024;
		int skip = 512;
		for (int i = 0; i < size; ++i) {
			output.write(1.99f);
		}
		input.skipFloats(skip);
		Assert.assertEquals(size-skip, input.available());
	}
	
	@Test
	public void clear() throws IOException {
		int size = 1024;
		for (int i = 0; i < size; ++i) {
			output.write(0.3333333333f);
		}
		input.skipFloats(input.available());
		Assert.assertEquals(0, input.available());
	}
	
	@Test(expected=EOFException.class)
	public void readTheEnd() throws IOException {
		float[] data = { 
			1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f
		};
		output.write(data);
		output.close();
		input.read(data);
		input.read(); // read one more than write: EOFException
	}

	
    /*+----------------------------------------------------------------------+
	  |                                                                      | 
	  | Negative tests for constructors.                                     | 
	  |                                                                      | 
	  +----------------------------------------------------------------------+*/
	
	@Test(expected=NullPointerException.class)
	public void outConstructorWithNull() throws IOException {
		new FloatOutputPipe(null);
	}
	
	@Test(expected=NullPointerException.class)
	public void inConstructorWithNull() throws IOException {
		new FloatInputPipe(null);
	}
	
	@Test(expected=IllegalArgumentException.class)
	public void constructorWithZeroSize() throws IOException {
		new FloatInputPipe(0);
	}
	
	@Test(expected=IllegalArgumentException.class)
	public void constructorWithPipeAndZeroSize() throws IOException {
		new FloatInputPipe(new FloatOutputPipe(), 0);
	}
	
	@Test(expected=IOException.class)
	public void outputConnect() throws IOException {
		FloatOutputPipe out = new FloatOutputPipe(new FloatInputPipe());
		new FloatInputPipe(out);
	}
	
	@Test(expected=IOException.class)
	public void inputConnect() throws IOException {
		FloatInputPipe in = new FloatInputPipe(new FloatOutputPipe());
		new FloatOutputPipe(in);
	}

	@Test(expected=IOException.class)
	public void invalidOutConnect() throws IOException {
		FloatOutputPipe out = new FloatOutputPipe();
		FloatInputPipe in = new FloatInputPipe(out);
		out.connect(in);
		out.connect(in);
	}

	@Test(expected=IOException.class)
	public void invalidInConnect() throws IOException {
		FloatOutputPipe out = new FloatOutputPipe();
		FloatInputPipe in = new FloatInputPipe(out);
		in.connect(out);
		in.connect(out);
	}

	@Test(expected=IOException.class)
	public void invalidConstructorConnect() throws IOException {
		FloatOutputPipe out = new FloatOutputPipe(new FloatInputPipe());
		new FloatInputPipe(out);
	}

    /*+----------------------------------------------------------------------+
	  |                                                                      | 
	  | Negative tests for the write and read methods.                       | 
	  |                                                                      | 
	  +----------------------------------------------------------------------+*/

	@Test(expected=NullPointerException.class)
	public void writeNullArray() throws IOException {
		output.write(null);
	}
	
	@Test(expected=NullPointerException.class)
	public void readNullArray() throws IOException {
		input.read(null);
	}
	
	@Test(expected=IndexOutOfBoundsException.class)
	public void tryReadSubArray() throws IOException {
		float[] data = { 
			1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f
		};
		output.write(data);		
		float[] buffer = new float[data.length];
		input.read(buffer, 0, buffer.length+1); // off-by-one error
	}
	
	@Test(expected=IndexOutOfBoundsException.class)
	public void tryWriteSubArray() throws IOException {
		float[] data = { 
			1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f
		};
		output.write(data, 5, data.length);		
	}

	@Test(expected=IllegalArgumentException.class)
	public void skipNegative() throws IOException {
		input.skipFloats(-1);
	}

}
